﻿using System;
using PostSharp.Laos;
using Castle.Services.Transaction;
using Castle.Windsor;
using log4net;
using DDDProject.Aspects.Utils;
using System.Reflection;

namespace DDDProject.Aspects
{
    [Serializable]
    public class UnitOfWorkAttribute : OnMethodInvocationAspect
    {
        protected static readonly ILog logger = LogManager.GetLogger(typeof(UnitOfWorkAttribute));

        private readonly Castle.Services.Transaction.IsolationMode _isolationMode;
        private readonly Castle.Services.Transaction.TransactionMode _transactionMode;

        /// <summary>
        /// Declares unspecified values for transaction and isolation.
        /// </summary>
        public UnitOfWorkAttribute()
            : this(Transactions.IsolationLevel.Unspecified, Transactions.TransactionMode.Unspecified)
        {
        }

        /// <summary>
        /// Declares the transaction mode, but omits the isolation.
        /// </summary>
        /// <param name="transactionMode"></param>
        public UnitOfWorkAttribute(Transactions.TransactionMode transactionMode)
            : this(Transactions.IsolationLevel.Unspecified, transactionMode)
        {
        }

        /// <summary>
        /// Declares both the transaction mode and isolation desired for this unit of work.
        /// </summary>
        public UnitOfWorkAttribute(
                        Transactions.IsolationLevel isolationLevel,
                        Transactions.TransactionMode transactionMode)
        {
            // --------------
            // Isolation Mode
            // --------------
            switch (isolationLevel)
            {
                case Transactions.IsolationLevel.ReadCommitted:
                    _isolationMode = Castle.Services.Transaction.IsolationMode.ReadCommitted;
                    break;
                case Transactions.IsolationLevel.Chaos:
                    _isolationMode = Castle.Services.Transaction.IsolationMode.Chaos;
                    break;
                case Transactions.IsolationLevel.ReadUncommitted:
                    _isolationMode = Castle.Services.Transaction.IsolationMode.ReadUncommitted;
                    break;
                case Transactions.IsolationLevel.Serializable:
                    _isolationMode = Castle.Services.Transaction.IsolationMode.Serializable;
                    break;
                case Transactions.IsolationLevel.Unspecified:
                    _isolationMode = Castle.Services.Transaction.IsolationMode.Unspecified;
                    break;
            }

            // ----------------
            // Transaction Mode
            // ----------------
            switch (transactionMode)
            {
                case Transactions.TransactionMode.NotSupported:
                    _transactionMode = Castle.Services.Transaction.TransactionMode.NotSupported;
                    break;
                case Transactions.TransactionMode.Requires:
                    _transactionMode = Castle.Services.Transaction.TransactionMode.Requires;
                    break;
                case Transactions.TransactionMode.RequiresNew:
                    _transactionMode = Castle.Services.Transaction.TransactionMode.RequiresNew;
                    break;
                case Transactions.TransactionMode.Supported:
                    _transactionMode = Castle.Services.Transaction.TransactionMode.Supported;
                    break;
                case Transactions.TransactionMode.Unspecified:
                    _transactionMode = Castle.Services.Transaction.TransactionMode.Unspecified;
                    break;
            }
        }

        /// <summary>
        /// Method interceptor 
        /// </summary>
        /// <param name="eventArgs">Event args that contains a delegate to the method that should 
        /// actually be called and the array of arguments</param>
        public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        {
            // Retrieve the Castle Transaction Manager
            ITransactionManager transactionManager = CastleUtils.GetTransactionManager();

            // Create transaction
            ITransaction transaction = transactionManager.CreateTransaction(
                                                                _transactionMode,
                                                                _isolationMode,
                                                                false);

            if (transaction == null)
            {
                eventArgs.Proceed();
                return;
            }

            transaction.Begin();

            bool rolledback = false;

            try
            {
                eventArgs.Proceed();

                if (transaction.IsRollbackOnlySet)
                {
                    logger.DebugFormat("Rolling back transaction {0}", transaction.GetHashCode());

                    rolledback = true;
                    transaction.Rollback();
                }
                else
                {
                    logger.DebugFormat("Committing back transaction {0}", transaction.GetHashCode());

                    transaction.Commit();
                }
            }
            catch (TransactionException ex)
            {
                // Whoops. Special case, let's throw without 
                // attempt to rollback anything

                if (logger.IsFatalEnabled)
                {
                    logger.Fatal("Fatal error during transaction processing", ex);
                }

                throw;
            }
            catch (TargetInvocationException ex)
            {
                if (!rolledback)
                {
                    logger.DebugFormat("Rolling back transaction {0} due to exception on method {2}.{1}", transaction.GetHashCode(), eventArgs.Delegate.Method.Name, eventArgs.Delegate.Method.DeclaringType.Name);

                    transaction.Rollback();
                }

                throw ex.InnerException;
            }
            finally
            {
                transactionManager.Dispose(transaction);
            }
        }
    }
}
